# 20. 生成器,生成器表达式,推导式


# 生成器


# 生成器函数

生成器函数:yield 返回值,这里要先说明一下,return跟yield的区别

return:直接返回结束,结束函数执行

yield:返回结果,每一个yield就是一段返回值,可以让函数分段执行

如果函数中包含了yield,那么这个函数就不是普通的函数了,而是生成器函数

注意:生成器函数中是无法使用return

def so():
    print("情这字伤人又伤心")
    yield "心最难治"
wo = so()
print(wo)

执行结果:
<generator object so at 0x0000027B5CAB3308>

由于函数中存在了yield,那么这个函数就是一个生成器函数,要执行这个函数的时候,就不能使用函数的执行了,而是去获取这个生成器函数,需要使用迭代器来获取,因为生成器的本质就是迭代器

def so():
    print("情这字伤人又伤心")
    yield "心最难治"
wo = so()
print(wo.__next__())
print(wo.__next__())

执行结果:
情这字伤人又伤心
心最难治
StopIteration

这样就实现了生成器函数的获取调用,这样又出现了一个很熟悉的问题,报错:StopIteration

因为上面实例连续获取二次,而生成器函数中的是以一个yield返回值函数为一段返回,第一次获取内容到yield返回值结束为第一次获取的结果,第二次去获取就获取不到数据,就会报错:StopIteration


# 实例(模拟生产)

一个厂家要生产一万件定制高端衣服,需要全记录在案

def so():
    wo = []
    for i in range(1,10001):
        wo.append("高端定制衣服"+ "_" + str(i))
    return wo
eo = so()
print(eo)

以上实例,结果虽然出得来,但是是一次性全出来这样会比较占用内存

def so():
    for i in range(1,10001):
        yield "高端定制衣服"+"_"+str(i)
wo = so()
print(wo.__next__())
print(wo.__next__())

以上实例,使用生成器函数,一次就会拿取一个,可以用多少生成多少,生成器是一个一个的指向下一个,不会回去,next()到哪,指针就到哪,下一次继续获取指针指向的值


# 另一种方法获取生成器中的内容 - send()

先说明一下send跟__next__的区别

next():可以让生成器向下执行一次

send:可以让生成器向下执行一次,并给上一个yield的位置传递一个值,在第一次执行生成器代码的时候不能使用send(),也不能在最后一个yield发送值

def so():
    print("早上想吃什么")
    a = yield "豆浆油条"
    print(a)
    print("中午想吃什么")
    b = yield "烤肉饭"
    print(b)
    print("晚上想吃什么")
    yield "火锅"
wo = so()
print(wo.__next__())
print(wo.send("———————————"))
print(wo.send("———————————"))

执行结果:
早上想吃什么
豆浆油条
———————————
中午想吃什么
烤肉饭
———————————
晚上想吃什么
火锅

​ 在以上实例中,send的特性就是不能在开头中使用,因为send要给上一个yield传递值,如果在开头中,还有上一个吗


# 生成器也可以用for来获取内容

def so():
    print("早上想吃什么")
    yield "豆浆油条"
    print("中午想吃什么")
    yield "烤肉饭"
    print("晚上想吃什么")
    yield "火锅"
wo = so()
for i in wo:
    print(i)
    
执行结果:
早上想吃什么
豆浆油条
中午想吃什么
烤肉饭
晚上想吃什么
火锅

# 使用列表来获取yield返回值

def so():
    yield "早上想吃什么"
    yield "豆浆油条"
    yield "中午想吃什么"
    yield "烤肉饭"
    yield "晚上想吃什么"
    yield "火锅"
wo = so()
woo = list(wo)
print(woo)

执行结果:
['早上想吃什么', '豆浆油条', '中午想吃什么', '烤肉饭', '晚上想吃什么', '火锅']


# 列表推导式

列表推导式是将循环判断语句合成一行代码

格式:变量 = [ 最终结果 for 变量 in 可迭代对象]

##获取0到15的值-正式循环
so = []
for i in range(16):
    so.append(i)
print(so)

##获取0到15的值-列表推导式
so = [ i for i in range(16)]
print(so)

执行结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

# 实例 - 无答案

  1. 使用列表推导式获取1-100内所有的偶数

  2. 使用列表推导式获取1-100内能被3整除的数

  3. 使用列表推导式100以内能被3整除的平方

  4. 寻找名字中带有两个 e 的人的名字

    names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
    

# 实例 - 答案

# 第一题

so = [ i for i in range(1,101) if i%2 == 0]
print(so)

执行结果:
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]

# 第二题

so = [ i for i in range(1,101) if i%3 == 0]
print(so)

执行结果:
[3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99]

# 第三题

so = [ i*i for i in range(1,101) if i%3 == 0]
print(so)

执行结果:
[9, 36, 81, 144, 225, 324, 441, 576, 729, 900, 1089, 1296, 1521, 1764, 2025, 2304, 2601, 2916, 3249, 3600, 3969, 4356, 4761, 5184, 5625, 6084, 6561, 7056, 7569, 8100, 8649, 9216, 9801]

# 第四题

so = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
wo = [ i for a in so for i in a if i.count("e") == 2]
print(wo)

执行结果:
['Jefferson', 'Wesley', 'Steven', 'Jennifer']


# 生成器表达式

生成器表达式和列表推导式的语法基本是一样的,只是把 [ ] 替换成 ( )

so = (i for i in range(1,16))
for i in so:
    print(i)

或

so = (i for i in range(1,16))
print(wo.__next__())
print(wo.__next__())
print(wo.__next__())
print(wo.__next__())
print(wo.__next__())
print(wo.__next__())
。。。。。

​ 以上二种方法执行都是可以的


# 生成器表达式的一个坑规则

def so():
    print(1024)
    yield 4201
wo = so()
wo1 = (i for i in wo)
wo2 = (i for i in wo1)
print(list(wo))
print(list(wo1))
print(list(wo2))

执行结果:
1024
[4201]
[]
[]

为什么会这样

def so():
    print(1024)
    yield 4201
wo = so()
wo1 = (i for i in wo)
wo2 = (i for i in wo1)
print(list(wo))
print(list(wo1))
print(list(wo2))

创建函数 so:
	打印数字 1024
    返回值 4201
引用生成器
使用生成器表达式循环数据源 wo
使用生成器表达式循环数据源 wo1
打印获取wo中的数据,这时so()生成器才会执行
打印获取wo1中的数据,wo1的数据来源是wo,但是wo已经取完了,那wo1也就没有数据了
打印获取wo2中的数据,wo2的数据来源是wo1,wo1的数据来源是wo,但是wo已经取完了,wo1也就没有数据了,wo2也就没有


# 生成器表达式和列表推导式的区别

  1. 列表推导式:比较消耗内存,一次加载,全部取出,得到的是一个列表
  2. 生成器表达式:几乎不占用内存,只有在使用的时候才会分配使用内存,得到的是一个生成器

相当于,要去买鸡蛋,列表推导式是直接给你一蓝子鸡蛋 ,生成器表达式则给你一个老母鸡,需要鸡蛋就给你下蛋

生成器的惰性机制:生成器只有在访问的时候才取值,就是说你找他要他才给你,不找他要,他懒得动一下



# 字典推导式

不用说,就是字典类型的推导式

so = {"a":1,"b":2}
wo = {so[i]: i for i in so}
print(wo)

执行结果:
{1: 'a', 2: 'b'}

​ 以上实例,把字典中的key跟value互换

so = [1,2,3,4,5]
wo = ["技术部","业务部","营销部","财务部","人力行政部"]
so_wo = { so[i]:wo[i] for i in range(len(so))}
print(so_wo)

执行结果:
{1: '技术部', 2: '业务部', 3: '营销部', 4: '财务部', 5: '人力行政部'}

​ 以上实例,将列表1中获取的数据跟列表2中的数据合并成一个字典



# 集合推导式

集合抓推导式可以帮我们直接生成一个集合

集合的特点:无序,不重复,所有集合,所以集合推导式自带去重功能

so = [1,2,3,1,2,5,6,4,5,6]
wo = { i for i in so }
print(wo)

执行结果:
{1, 2, 3, 4, 5, 6}


# 题目

最后来一个很难的练习题目来结尾

def so(a,b):
    return a + b

def wo():
    for ro in range(4):
        yield ro

s = wo()

for i in [5,12]:
    s = (so(i,n) for n in s)

print(list(s))

​ 建议先想出答案才去执行得到结果